

#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "../mac.h"

#include "core_mqtt.h"
#include "core_mqtt_state.h"
#include "network_transport.h"
#include "backoff_algorithm.h"

#include "clock.h"

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"

#include "aws_iot_Publish.h"
#include "MqttPublish_Data.h"


static const char *TAG = "AWS IoT客户端";
MqttPublish_Msg_S AWS_IoT_PublishMsg = {0};

/**
 * @brief Structure to keep the MQTT publish packets until an ack is received
 * for QoS1 publishes.
 */
typedef struct PublishPackets
{
    // Packet identifier of the publish packet.
    uint16_t packetId;
    // Publish info of the publish packet.
    MQTTPublishInfo_t pubInfo;
} PublishPackets_t;
/**
 * @brief Array to keep the outgoing publish messages.
 * These stored outgoing publish messages are kept until a successful ack
 * is received.
 */
static PublishPackets_t outgoingPublishPackets[5] = {0};

char* pub = "heartbeat/";
char publishName[publishName_Length];

char* pubData = "发布测试数据";
uint16_t pubDataLength = 6*2;


char* PublishTopicName(void)
{
    int i = 0;
    for (i = 0; i < pub_Length; i++) publishName[i] = pub[i];
    for (; i < publishName_Length; i++) publishName[i] = WIFI_MAC[i - pub_Length];

    ESP_LOGE(TAG,"MQTT客户端标识: %s", publishName);
    return publishName;
}

int handlePublishResend(MQTTContext_t *pMqttContext)
{
    int returnStatus = EXIT_SUCCESS;
    MQTTStatus_t mqttStatus = MQTTSuccess;
    uint8_t index = 0U;
    MQTTStateCursor_t cursor = MQTT_STATE_CURSOR_INITIALIZER;
    uint16_t packetIdToResend = 0;
    bool foundPacketId = false;

    assert(pMqttContext != NULL);
    assert(outgoingPublishPackets != NULL);

    /* MQTT_PublishToResend() provides a packet ID of the next PUBLISH packet
     * that should be resent. In accordance with the MQTT v3.1.1 spec,
     * MQTT_PublishToResend() preserves the ordering of when the original
     * PUBLISH packets were sent. The outgoingPublishPackets array is searched
     * through for the associated packet ID. If the application requires
     * increased efficiency in the look up of the packet ID, then a hashmap of
     * packetId key and PublishPacket_t values may be used instead. */
    packetIdToResend = MQTT_PublishToResend(pMqttContext, &cursor);

    while (packetIdToResend != 0)
    {
        foundPacketId = false;

        for (index = 0U; index < 5; index++)
        {
            if (outgoingPublishPackets[index].packetId == packetIdToResend)
            {
                foundPacketId = true;
                outgoingPublishPackets[index].pubInfo.dup = true;

                ESP_LOGE(TAG,"发送带有数据包 ID 的重复发布 %u.", outgoingPublishPackets[index].packetId);
                mqttStatus = MQTT_Publish(pMqttContext, &outgoingPublishPackets[index].pubInfo, outgoingPublishPackets[index].packetId);

                if (mqttStatus != MQTTSuccess)
                {
                    ESP_LOGE(TAG,"Sending duplicate PUBLISH for packet id %u failed with status %s.", outgoingPublishPackets[index].packetId,MQTT_Status_strerror(mqttStatus));
                    returnStatus = EXIT_FAILURE;
                    break;
                }
                else
                {
                    LogInfo(("Sent duplicate PUBLISH successfully for packet id %u.\n\n",
                             outgoingPublishPackets[index].packetId));
                }
            }
        }

        if (foundPacketId == false)
        {
            ESP_LOGE(TAG,"Packet id %u requires resend, but was not found in outgoingPublishPackets.", packetIdToResend);
            returnStatus = EXIT_FAILURE;
            break;
        }
        else
        {
            /* Get the next packetID to be resent. */
            packetIdToResend = MQTT_PublishToResend(pMqttContext, &cursor);
        }
    }

    return returnStatus;
}

static int getNextFreeIndexForOutgoingPublishes(uint8_t *pIndex)
{
    int returnStatus = EXIT_FAILURE;
    uint8_t index = 0;

    assert(outgoingPublishPackets != NULL);
    assert(pIndex != NULL);

    for (index = 0; index < 5; index++)
    {
        /* A free index is marked by invalid packet id.
         * Check if the the index has a free slot. */
        if (outgoingPublishPackets[index].packetId == 0)
        {
            returnStatus = EXIT_SUCCESS;
            break;
        }
    }

    /* Copy the available index into the output param. */
    *pIndex = index;

    return returnStatus;
}

static void cleanupOutgoingPublishAt(uint8_t index)
{
    assert(outgoingPublishPackets != NULL);
    assert(index < 5);

    /* Clear the outgoing publish packet. */
    (void)memset(&(outgoingPublishPackets[index]),
                 0x00,
                 sizeof(outgoingPublishPackets[index]));
}
void cleanupOutgoingPublishes(void)
{
    assert(outgoingPublishPackets != NULL);

    /* Clean up all the outgoing publish packets. */
    (void)memset(outgoingPublishPackets, 0x00, sizeof(outgoingPublishPackets));
}
void cleanupOutgoingPublishWithPacketID(uint16_t packetId)
{
    uint8_t index = 0;

    assert(outgoingPublishPackets != NULL);
    assert(packetId != 0);

    /* Clean up all the saved outgoing publishes. */
    for (; index < 5; index++)
    {
        if (outgoingPublishPackets[index].packetId == packetId)
        {
            cleanupOutgoingPublishAt(index);
            LogInfo(("Cleaned up outgoing publish packet with packet id %u.\n\n",
                     packetId));
            break;
        }
    }
}

void aws_Publish(MQTTContext_t *pMqttContext)
{
    uint8_t publishIndex = 5;
    int returnStatus = EXIT_SUCCESS;
    MQTTStatus_t mqttStatus = MQTTSuccess;

    /* Get the next free index for the outgoing publish. All QoS1 outgoing
    * publishes are stored until a PUBACK is received. These messages are
    * stored for supporting a resend if a network connection is broken before
    * receiving a PUBACK. */
    returnStatus = getNextFreeIndexForOutgoingPublishes(&publishIndex);
    if (returnStatus == EXIT_FAILURE)
    {
        ESP_LOGE(TAG,"Unable to find a free spot for outgoing PUBLISH message.\n\n");
    }
    else
    {
        AWS_IoT_PublishMsg.PubMsg_Squ++;

        /* This example publishes to only one topic and uses QOS1. */
        outgoingPublishPackets[publishIndex].pubInfo.qos = MQTTQoS1;
        outgoingPublishPackets[publishIndex].pubInfo.pTopicName = publishName;
        outgoingPublishPackets[publishIndex].pubInfo.topicNameLength = publishName_Length;
        outgoingPublishPackets[publishIndex].pubInfo.pPayload = MqttPublish_Encode(AWS_IoT_PublishMsg);
        outgoingPublishPackets[publishIndex].pubInfo.payloadLength = MqttPublish_BufLength;
        // Get a new packet id.
        outgoingPublishPackets[publishIndex].packetId = MQTT_GetPacketId(pMqttContext);
        // ESP_LOGE(TAG,"发布数据到主题 %.*s ", outgoingPublishPackets[publishIndex].pubInfo.topicNameLength, outgoingPublishPackets[publishIndex].pubInfo.pTopicName);
        mqttStatus = MQTT_Publish(pMqttContext, &outgoingPublishPackets[publishIndex].pubInfo, outgoingPublishPackets[publishIndex].packetId);
        if (mqttStatus != MQTTSuccess)
        {
            ESP_LOGE(TAG,"MQTT 发布失败!, error = %s.", MQTT_Status_strerror(mqttStatus));
            cleanupOutgoingPublishAt(publishIndex);
            cleanupOutgoingPublishes();
            returnStatus = EXIT_FAILURE;
        }
        else
        {
            // ESP_LOGE(TAG,"MQTT 发布成功");
            // ESP_LOGE(TAG,"建立干净的 MQTT会话发布缓存, 清理发布缓存...\n\n");
            // 清理等待 ack 的传出发布，因为此新连接不会重新建立现有会话.
            cleanupOutgoingPublishes();
        }
    }
}

void PublishLoop(MQTTContext_t *pMqttContext)
{
    int returnStatus = EXIT_SUCCESS;
    MQTTStatus_t mqttStatus = MQTTSuccess;

    PublishTopicName();
    for (;;)
    {
        aws_Publish(pMqttContext);
        mqttStatus = MQTT_ProcessLoop(pMqttContext, 400);
        if (mqttStatus != MQTTSuccess)
        {
            ESP_LOGE(TAG,"退出循环, MQTT_ProcessLoop 返回状态 = %s.", MQTT_Status_strerror(mqttStatus));
            returnStatus = EXIT_FAILURE;
            break;
        }
    }
    return returnStatus;
}
